using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using DevExpress.Xpo;

namespace XpoOneWayMultiAssociations
{
   /// <summary>
   /// Contains extension methods for use with AOP.
   /// </summary>
   internal static class ExtensionMethods
   {
      #region Static Methods

      /// <summary>
      /// Determines whether a property is declared in a specific type.
      /// </summary>
      /// <param name="property">The property.</param>
      /// <param name="declaringType">The type declaring in which the property should be declared.</param>
      internal static bool DeclaredIn(this PropertyInfo property, Type declaringType)
      {
         return property.DeclaringType == declaringType;
      }

      /// <summary>
      /// Gets the type of a collection property's target.
      /// </summary>
      /// <param name="property">The property.</param>
      /// <returns></returns>
      internal static Type GetCollectionTargetType(this PropertyInfo property)
      {
         return property.PropertyType.GetGenericArguments()[0];
      }

      /// <summary>
      /// Determines whether a property has an associated <see cref="AssociationAttribute"/>.
      /// </summary>
      /// <param name="property">The property.</param>
      internal static bool HasAssociationAttribute(this PropertyInfo property)
      {
         return Attribute.GetCustomAttribute(property, typeof (AssociationAttribute)) != null;
      }

      /// <summary>
      /// Determines whether a type implements an interface.
      /// </summary>
      /// <typeparam name="T">The interface type.</typeparam>
      /// <param name="type">The type.</param>
      /// <example><code>bool isNotifier = instance.GetType().ImplementsInterface&lt;INotifyPropertyChanged&gt;();</code></example>
      /// <returns></returns>
      internal static bool ImplementsInterface<T>(this Type type)
      {
         return ImplementsInterface(type, typeof (T));
      }

      /// <summary>
      /// Determines whether a type implements an interface.
      /// </summary>
      /// <param name="interfaceType">The interface type.</typeparam>
      /// <param name="type">The type.</param>
      /// <example><code>bool isNotifier = instance.GetType().ImplementsInterface(typeof(INotifyPropertyChanged));</code></example>
      /// <returns></returns>
      internal static bool ImplementsInterface(this Type type, Type interfaceType)
      {
         return interfaceType.IsInterface && type.GetInterface(interfaceType.FullName) != null;
      }

      /// <summary>
      /// Determines whether the specified property's property type is collection type.
      /// </summary>
      /// <param name="property">The property.</param>
      internal static bool IsCollection(this PropertyInfo property)
      {
         return IsCollection(property.PropertyType);
      }

      /// <summary>
      /// Determines whether the specified type is a collection.
      /// </summary>
      /// <param name="type">The type.</param>
      internal static bool IsCollection(this Type type)
      {
         return
            type.ImplementsInterface<ICollection>() ||
            type.ImplementsInterface<IList>() ||
            type.IsGenericCollection();
      }

      /// <summary>
      /// Determines whether the specified property's property type is collection type (and is readable!).
      /// </summary>
      /// <param name="property">The property.</param>
      internal static bool IsDomainCollection(this PropertyInfo property)
      {
         if (!property.CanRead) return false;
         var propertyType = property.PropertyType;
         return
            typeof (XPCollection).IsAssignableFrom(propertyType) || (
                                                                       propertyType.IsGenericType &&
                                                                       typeof (XPCollection<>).IsAssignableFrom(
                                                                          propertyType.GetGenericTypeDefinition()));
      }

      /// <summary>
      /// Determines whether a type implements <see cref="System.Collections.Generic.ICollection{T}"/>.
      /// </summary>
      /// <param name="type">The type.</param>
      /// <returns></returns>
      internal static bool IsGenericCollection(this Type type)
      {
         var interfaces = type.GetInterfaces();
         foreach (var interfaceType in interfaces)
         {
            if (
               interfaceType.IsGenericType &&
               interfaceType.GetGenericTypeDefinition() == typeof (ICollection<>)
               )
               return true;
         }
         return false;
      }

      /// <summary>
      /// Determines whether a property is one way association.
      /// </summary>
      /// <param name="property">The property.</param>
      internal static bool IsOneWayAssociation(this PropertyInfo property)
      {
         return IsDomainCollection(property) && !HasAssociationAttribute(property);
      }

      #endregion
   }
}